//	--------------------------------------------------------------------
//	Copyright(C) 2006,2007 Zhao Yukun. All rights reserved.
//	This file is a part of the Xeres system.
//	Xeres 2007
//	Contact : xeres.engine@gmail.com
//	$(reservedInfo)
//	$(reservedDeclaration)
//	--------------------------------------------------------------------
///	@file	<loop_scheduler.cpp>
///	@path	~/src/engine/xcore/
///	@date	2008/03/21
///	@desc	.

#include "config/config.h"

#include "lib/utilities/debug.h"
#include "lib/utilities/observer.h"
#include "lib/system/sysinfo.h"
#include "lib/system/thread_debug.h"

#include "engine/xcore/loop_scheduler.h"

#define	ENGINE_PARALLEL_TICK		1
#define	ENGINE_PARALLEL_RENDER		2


namespace xeres {

	// LoopScheduler
	LoopScheduler::LoopScheduler( void )
		: m_parallelize( 0 )
		, m_loopThread( NULL )
	{
	}
	// ~LoopScheduler
	LoopScheduler::~LoopScheduler( void )
	{

	}

	// ConfigMultiTasking
	void LoopScheduler::ConfigMultiTasking( const WString& strategy , int thread_count , bool config_affinity )
	{
		TRACK_FUNCTION_SCOPE();
		RUNNING_ON_THREAD( _S("main") );

		std::vector<int> avail_core;
		std::vector<bool> pool_affinity;
		int count = get_available_core( pool_affinity );
		bool lower_priority = false;
		// make available core list
		for( size_t i = 0 , total = pool_affinity.size() ; i < total ; ++i )
		{
			if( pool_affinity[i] )
				avail_core.push_back( (int)i );
		}
		// dispatching core
		switch( count )
		{
		case 1:
			// maybe single core
			{
				TRACE_INFO( _S("Single core available, no multi-tasking optimize...") );
				// should we disable parallelism?
				if( strategy == _S("tick") || strategy == _S("render") )
				{
					TRACE_WARNING( _S("Engine::ConfigMultiTasking: Force parallelized engine running on single core may cause performance problem.") );
					if( strategy == _S("tick") )
						SetTickParallelized();
					else
						SetRenderParallelized();

					// set parallelized thread affinity
					m_loopThread->SingleAffinity( avail_core[0] );
				}
				else
				{
					TRACE_INFO( _S("Engine::ConfigMultiTasking: Turn off parallel mechanism by single core environment.") );
				}
			} // case
			break;
		case 2:
			// maybe duo core
			{
				TRACE_INFO( _S("Duo core found...") );
				if( strategy == _S("none") )
				{
					// no parallelism, dispatch main thread & background threads to 2 cores.
					if( config_affinity )
					{
						Thread::GetCurrent()->SingleAffinity( avail_core[0] );
						// and disable processor for render thread preserved
						pool_affinity[avail_core[0]] = false;
					}
				}
				else
				{
					// In this case, we'd like to make 'tick' running with background threads on the same processor,
					// render thread can be run independently.
					if( strategy == _S("tick") ) // parallelize tick
					{
						SetTickParallelized();
						if( config_affinity )
						{
							Thread::GetCurrent()->SingleAffinity( avail_core[0] );
							m_loopThread->SingleAffinity( avail_core[1] );
							// and disable processor for render thread preserved
							pool_affinity[ avail_core[0] ] = false;
						}
					}
					else if( strategy == _S("render") || strategy == _S("auto") ) // parallelize render
					{
						SetRenderParallelized();
						if( config_affinity )
						{
							Thread::GetCurrent()->SingleAffinity( avail_core[1] );
							m_loopThread->SingleAffinity( avail_core[0] );
							// and disable processor for render thread preserved
							pool_affinity[ avail_core[1] ] = false;
						}
					} // 'tick'
				} // 'none'
			} // case
			break;
		case 3:
		case 4:
		default:
			// maybe triple/quad or more cores
			{
				TRACE_INFO( _S("Multi-core found...") );
				if( strategy == _S("none") )
				{
					// no parallelism, dispatch main thread & background threads to 2 cores.
					if( config_affinity )
					{
						Thread::GetCurrent()->SingleAffinity( avail_core[0] );
						// and disable processor for render thread preserved
						pool_affinity[avail_core[0]] = false;
					}
				}
				else
				{
					// 3 cores available, we can use 2 of tick & render, 1 for background thread.
					if( strategy == _S("tick") ) // parallelize tick
					{
						SetTickParallelized();
						if( config_affinity )
						{
							Thread::GetCurrent()->SingleAffinity( avail_core[0] );
							m_loopThread->SingleAffinity( avail_core[1] );
							// and disable processor for render thread preserved
							pool_affinity[ avail_core[0] ] = false;
							pool_affinity[ avail_core[1] ] = false;
						}
					}
					else if( strategy == _S("render") || strategy == _S("auto") ) // parallelize render
					{
						SetRenderParallelized();
						if( config_affinity )
						{
							Thread::GetCurrent()->SingleAffinity( avail_core[1] );
							m_loopThread->SingleAffinity( avail_core[0] );
							// and disable processor for render thread preserved
							pool_affinity[ avail_core[0] ] = false;
							pool_affinity[ avail_core[1] ] = false;
						}
					} // 'tick'
				} // 'none'
			} // case
			break;
		}

		// config task manager
		switch( count )
		{
		case 1:
			// threads sharing the same core, background threads should have lower priority
			lower_priority = true;
			break;
		case 2:
			// not enough core for us to dispatch background threads on single core
			lower_priority = true;
			break;
		}
		TaskManager::Initialize( thread_count * 10 , thread_count ,  lower_priority , config_affinity ? &pool_affinity : NULL );
	}

	// FirstFrame
	void LoopScheduler::FirstFrame( void )
	{
		// config threads
		if( m_parallelize == ENGINE_PARALLEL_TICK )
		{
			DEFINE_THREAD_CHECK( _S("render") );
		}
		else if( m_parallelize == ENGINE_PARALLEL_RENDER )
		{
			DEFINE_THREAD_CHECK( _S("tick") );
		}
		else
		{
			DEFINE_THREAD_CHECK( _S("tick") );
			DEFINE_THREAD_CHECK( _S("render") );
		}

		// start loop thread
		if( m_loopThread )
		{
			m_loopThread->Start();
		}
	}

	// LastFrame
	void LoopScheduler::LastFrame( void )
	{
		// close tick thread
		if( m_parallelize )
		{
			m_parallelize = false;
			m_loopSignal.SetSignal();
			m_loopThread->Wait();
			delete m_loopThread;
		}

		// Reset tick stages.
		TickScheduler::ResetStages();
	}

	// DoTick
	void LoopScheduler::DoTick( void )
	{
		COUNTER_GUARD( _S("do_tick") );

		// pre

		// do
		TickScheduler::TickFrame();

		// post
		TaskManager::Resolve( Time(0) );
	}

	// DoRender
	void LoopScheduler::DoRender( void )
	{
		COUNTER_GUARD( _S("do_render") );
		// pre

		// do
		RenderScheduler::RenderFrame();

		//post
	}

	// update frame
	void LoopScheduler::UpdateFrame( void )
	{
		TRACK_FUNCTION_SCOPE();
		RUNNING_ON_THREAD( _S("main") );

		bool enable_render = true;

		// update frame by frame control
		enable_render = FrameControl::UpdateFrame() && RenderScheduler::IsEnableRender();

		// Resolve tick stages if needed.
		ResolveLoopStage();

		// Root calls.
		TickScheduler::UpdateRootCall();

		// pause main
		THREAD_CHECK_AFFECT( _S("main") , false );

		if( !enable_render )
		{
			// tick only
			DoTick();
		}
		else if( m_parallelize == 0 )
		{
			// tick
			DoTick();
			// render
			DoRender();
		}
		else if( m_parallelize == ENGINE_PARALLEL_TICK )
		{
			ParallelTick();
		}
		else if( m_parallelize == ENGINE_PARALLEL_RENDER )
		{
			ParallelRender();
		}
		else
		{
			assert(false);
		}

		// resume main
		THREAD_CHECK_AFFECT( _S("main") , true );

		// sync call
		UpdateSyncCall();
	}

	// for tick parallelized
	bool LoopScheduler::SetTickParallelized( void )
	{
		RUNNING_ON_THREAD( _S("main") );

		if( m_parallelize != 0 )
		{
			TRACE_WARNING( _S("Engine::SetTickParallelized: Duplicate 'Set???Parallelized' called.") );
			return true;
		}

		m_parallelize = ENGINE_PARALLEL_TICK;

		TRACE_INFO( _S("Engine adopt tick thread mode.") );

		// create new thread
		m_loopThread = new ProcThread( bind( &LoopScheduler::TickThread , this ) );
		m_loopThread->Create();

		return true;
	}

	// for render parallelized
	bool LoopScheduler::SetRenderParallelized( void )
	{
		RUNNING_ON_THREAD( _S("main") );

		if( m_parallelize != 0 )
		{
			TRACE_WARNING( _S("Engine::SetRenderParallelized: Duplicate 'Set???Parallelized' called.") );
			return true;
		}

		m_parallelize = ENGINE_PARALLEL_RENDER;

		TRACE_INFO( _S("Engine adopt render thread mode.") );

		// create new thread
		m_loopThread = new ProcThread( bind( &LoopScheduler::RenderThread , this ) );
		m_loopThread->Create();

		return true;
	}

	// render thread
	void LoopScheduler::RenderThread( void )
	{
		DEFINE_THREAD_CHECK( _S("render") );
		DEFINE_THREAD_CHECK( _S("_render") );

		ThreadPool::InitializeThisThread( "render" );

		// loop
		while( m_loopSignal.Wait() , m_parallelize )
		{
			DoRender();
			m_loopSyncSignal.SetSignal();
		}

		ThreadPool::FinalizeThisThread();
	}

	// tick thread
	void LoopScheduler::TickThread( void )
	{
		DEFINE_THREAD_CHECK( _S("tick") );
		DEFINE_THREAD_CHECK( _S("_tick") );

		ThreadPool::InitializeThisThread( "tick" );

		// loop
		while( m_loopSignal.Wait() , m_parallelize )
		{
			DoTick();
			m_loopSyncSignal.SetSignal();
		}

		ThreadPool::FinalizeThisThread();
	}

	// parallel tick
	void LoopScheduler::ParallelTick( void )
	{
		// this thread runs render as tick is parallelized
		m_loopSignal.SetSignal();

		DoRender();

		// synchronize
		m_loopSyncSignal.Wait();
	}

	// parallel render
	void LoopScheduler::ParallelRender( void )
	{
		// this thread runs tick as render is parallelized
		m_loopSignal.SetSignal();

		DoTick();

		// synchronize
		m_loopSyncSignal.Wait();
	}

} // namespace xeres
